#pragma once

#include <iostream>
#include <memory>
#include "Socket.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"

using namespace socket_ns;
using namespace log_ns;

class SelectServer
{
      const static int gnum = sizeof(fd_set) * 8; // 位图的大小
      const static int gdefault = -1;

public:
      SelectServer(uint16_t port) : _port(port), _listensock(std::make_unique<TcpSocket>())
      {
            _listensock->BuildListenSocket(_port);
      }
      ~SelectServer()
      {
      }
      void InitServer()
      {
            // 把辅助数组初始化
            for (int i = 1; i < gnum; i++)
                  fd_array[i] = gdefault;

            fd_array[0] = _listensock->Sockfd(); // 默认直接添加listensock到数组中
      }
      // 处理新连接的
      void Accepter()
      {
            // 我们叫做连接事件就绪，等价于读事件就绪
            InetAddr addr;
            int sockfd = _listensock->Accepter(&addr); // 一定不会阻塞!
            if (sockfd > 0)
            {
                  LOG(DEBUG, "get a new link, client info: %s:%d\n", addr.Ip().c_str(), addr.Port());

                  // 如何把新的fd添加select，由select进行统一监管？
                  // 只要把新的fd，添加到fd_array中即可!
                  bool flag = false;
                  for (int pos = 1; pos < gnum; pos++)
                  {
                        if (fd_array[pos] == gdefault)
                        {
                              flag = true;
                              fd_array[pos] = sockfd;
                              LOG(INFO, "add %d fd_array success!\n", sockfd);
                              break;
                        }
                  }
                  if (!flag)
                  {
                        LOG(WARNING, "Server Is Full!\n");
                        ::close(sockfd);
                  }
            }
      }
      // 处理普通fd读写的
      void HandlerIO(int i)
      {
            // 普通fd，正常的读写
            char buffer[1024];
            ssize_t n = ::recv(fd_array[i], buffer, sizeof(buffer), 0);
            if (n > 0)
            {
                  buffer[n] = 0;
                  std::cout << "client say# " << std::endl;
                  std::string echo_str = "[server echo info]# ";
                  echo_str += buffer;
                  // 写事件就绪，关系的是发送缓冲区里有没有空间
                  // 对于一个新文件fd时，缓冲区里默认是空的，则写事件默认就绪
                  ::send(fd_array[i], echo_str.c_str(), echo_str.size(), 0); // 临时方案
            }
            else if (n == 0)
            {
                  LOG(INFO, "client quit...\n");
                  // 1. 关闭fd
                  ::close(fd_array[i]);
                  // 2. select不要再关心这个fd了
                  fd_array[i] = gdefault;
            }
            else
            {
                  LOG(ERROR, "recv error!\n");
                  fd_array[i] = gdefault;

            }
      }

      // 一定会存在大量的fd就绪，可能是普通fd,也可能是listensockfd
      void HanderEvent(fd_set &rfds)
      {
            for (int i = 0; i < gnum; i++)
            {
                  if (fd_array[i] == gdefault)
                        continue;
                  // fd一定是合法的
                  // 但是合法的fd不一定就绪，如何判断fd是否就绪？
                  if (FD_ISSET(fd_array[i], &rfds))
                  {
                        // 读事件就绪
                        // 判断是普通fd？还是listensockfd？
                        if (_listensock->Sockfd() == fd_array[i])
                        {
                              Accepter();
                        }
                        else
                        {
                              HandlerIO(i);
                        }
                  }
            }
      }

      void Loop()
      {
            // 1.文件描述符进行初始化
            fd_set rfds;
            FD_ZERO(&rfds);
            int max_fd = gdefault;

            // 2.把合法的fd，添加到rfds中
            for (int i = 0; i < gnum; i++)
            {
                  if (fd_array[i] == gdefault)
                        continue;
                  FD_SET(fd_array[i], &rfds);
                  // 2.1更新出最大的文件fd的值
                  if (max_fd < fd_array[i])
                        max_fd = fd_array[i];
            }

            struct timeval timeout = {5, 0};

            while (true)
            {
                  int n = ::select(max_fd + 1, &rfds, nullptr, nullptr, /*&timeout*/ nullptr);
                  switch (n)
                  {
                  case 0:
                        LOG(DEBUG, "time out, %d.%d\n", timeout.tv_sec, timeout.tv_usec); // 超时
                        break;
                  case -1:
                        LOG(ERROR, "select error\n");
                        break;
                  default:
                        LOG(INFO, "heaved event ready, n = %d\n", n); // 如果事件就绪，但是不处理，select会一直通知，直到处理
                        HanderEvent(rfds);
                        PrintDebug();
                        sleep(1);
                        break;
                  }
            }
      }

      void PrintDebug()
      {
            std::cout << "fd list: ";
            for (int i = 0; i < gnum; i++)
            {
                  if (fd_array[i] == gdefault)
                        continue;
                  std::cout << fd_array[i] << " ";
            }
            std::cout << "\n";
      }

private:
      uint16_t _port;
      std::unique_ptr<Socket> _listensock;

      // 1.select要正常工作，需要借助一个辅助数组，来保存所有合法的fd
      int fd_array[gnum];
};